use std::fmt::Debug;

use serde::{Deserialize, Serialize};
use serde_xml_rs::from_str;
use serde_json::json;
use serde_json::Value;

use super::super::my_err::MyErr;
use super::super::my_dbs::Mdb;
use super::super::my_cache; 
use super::super::util;
use super::super::util_date;
use super::super::util_string;
use super::super::wechat::Wechat;
use super::open::AUTHORIZER_ACCESS_TOKEN;

const JS_API_TICKET_KEY: &str = "jsapiticket#key"; // 缓存中存放 js_api_ticket 的key

const GET_CURRENT_MENU_URL: &str = "https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token="; // 获取当前公众号菜单配置的URL地址
const SET_MENU_URL: &str = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token="; // 设置公众号菜单配置的URL地址
const SEND_MESSAGE_TEMPLATE_URL: &str = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token="; // 发送模板消息的URL地址 

/// 验证票据加密前 xml
#[derive(Debug, Serialize, Deserialize)]
pub struct MessageEncXML {
    #[serde(rename = "ToUserName")]
    to_user_name: String,
    #[serde(rename = "Encrypt")]
    encrypt: String,
}

/// 验证票据解密后 xml
#[derive(Debug, Serialize, Deserialize)]
pub struct MessageDecXML {
    #[serde(default, rename = "ToUserName")]
    to_user_name: String, // 开发者微信号
    #[serde(rename = "FromUserName")]
    from_user_name: String, // 发送方帐号（一个OpenID）
    #[serde(rename = "CreateTime")]
    create_time: i64, // 消息创建时间 （整型:单位秒）
    #[serde(rename = "MsgType")]
    msg_type: String, // 消息类型
    #[serde(default, rename = "MsgId")]
    msg_id: i64, // 消息id，64位整型

    #[serde(default, rename = "Content")]
    content: String, // 文本消息内容
    #[serde(default, rename = "PicUrl")]
    pic_url: String, // 图片链接（由系统生成）
    #[serde(default, rename = "MediaId")]
    media_id: String, // 视频消息媒体id，可以调用获取临时素材接口拉取数据。
    #[serde(default, rename = "Format")]
    format: String, // 语音格式：amr
    #[serde(default, rename = "Recognition")]
    recognition: String, // 语音识别结果，UTF8编码
    #[serde(default, rename = "ThumbMediaId")]
    thumb_media_id: String, // 视频消息缩略图的媒体id，可以调用获取临时素材接口拉取数据。
    #[serde(default, rename = "Location_X")]
    location_x: String, // 地理位置纬度
    #[serde(default, rename = "Location_Y")]
    location_y: String, // 地理位置经度
    #[serde(default, rename = "Scale")]
    scale: String, // 地图缩放大小
    #[serde(default, rename = "Label")]
    label: String, // 地理位置信息
    #[serde(default, rename = "Title")]
    title: String, // 消息标题
    #[serde(default, rename = "Description")]
    description: String, // 消息描述
    #[serde(default, rename = "Url")]
    url: String, // 消息链接

    #[serde(default, rename = "Event")]
    event: String, // 事件类型
    #[serde(default, rename = "EventKey")]
    event_key: String, // 事件key
    #[serde(default, rename = "MenuId")]
    menu_id: String, // 菜单ID
} 

/// 微信公众号
#[derive(Deserialize)]
pub struct Mp {
    pub wechat: Wechat,
    pub authorizer_app_id: String, // 授权方appid
}

impl Mp {  
    
    /// 获取授权方令牌（authorization_access_token 即公众号的 access_token）
    async fn get_authorization_access_token(&self) -> Result<String, MyErr> {
        if let Some(s) = my_cache::get(&format!("{}{}", self.authorizer_app_id, AUTHORIZER_ACCESS_TOKEN)) {
            log::info!("缓存中已经存在 authorization_access_token ，直接获取并返回: {}", s);
            Ok(s)
        } else {
            // 如果缓存中没有，则从数据库中读取
            let row = Mdb::table("weixin_authorizer")
                .where_add_str("authorizer_appid = ?", json!([self.authorizer_app_id]))
                .one_force::<Value>().await?;

            // 重新存入缓存中
            let end_timestamp = row.get("end_timestamp").and_then(Value::as_i64).unwrap_or_default();
            let authorizer_access_token = row.get("authorizer_access_token").and_then(Value::as_str).unwrap_or_default();
            let expires_in = end_timestamp - util_date::now_seconds();
            my_cache::set(&format!("{}{}", self.authorizer_app_id, AUTHORIZER_ACCESS_TOKEN), authorizer_access_token.to_string(), expires_in);

            log::info!("从数据库中读取 authorization_access_token ，返回: {}", authorizer_access_token);
            Ok(authorizer_access_token.to_string())
        }
    }

    /// 获取微信公众号页面调用weix js sdk 需要的 js_api_ticket 
    async fn get_js_api_ticket(&self) -> Result<String, MyErr> {
        if let Some(s) = my_cache::get(JS_API_TICKET_KEY) {
            log::info!("缓存中已经存在 js_api_ticket ，直接获取并返回: {}", s);
            Ok(s)
        } else {
            let access_token = self.get_authorization_access_token().await?;

            let url = format!("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={}&type=jsapi", access_token); 

            let obj = super::get_json_to_url(&url).await?;
            log::info!("js_api_ticket 得到结果数据：{:?}", obj);

            if let Some(v) = obj.get("ticket").and_then(Value::as_str) {
                let expires_in = obj.get("expires_in").and_then(Value::as_i64).unwrap_or(600); // 获取有效期，单位秒 
                my_cache::set(JS_API_TICKET_KEY, v.to_string(), expires_in); // 有效期2小时
                Ok(v.to_string())
            } else {
                Err(MyErr::Msg(format!("无法获取 js_api_ticket ：{:?}", obj)))
            }
        }
    }

    /// 读取公众号当前菜单配置
    pub async fn get_menu(&self) -> Result<Value, MyErr> {
        let authorization_access_token = self.get_authorization_access_token().await?;
        let url = format!("{}{}", GET_CURRENT_MENU_URL, authorization_access_token);

        let obj = super::get_json_to_url(&url).await?;
        Ok(obj)
    }

    /// 设置公众号菜单
    pub async fn set_menu(&self, config:Value) -> Result<i64, MyErr> {
        let authorization_access_token = self.get_authorization_access_token().await?;
        let url = format!("{}{}", SET_MENU_URL, authorization_access_token);

        let obj = super::post_json_to_url(&url, config).await?; 

        let errcode = obj.get("errcode").and_then(Value::as_i64).unwrap_or(-1);
        if errcode == -1 {
            Err(MyErr::Msg(format!("设置菜单失败，服务器返回信息:{:?}", obj)))
        } else if errcode == 0 {
            Ok(errcode)
        } else {
            let errmsg = obj.get("errmsg").and_then(Value::as_str).unwrap_or_default();
            Err(MyErr::Msg(format!("错误代码: {}, 内容{}", errcode, errmsg)))
        } 
    }

    /// 发送模板消息
    /// touser: 接收消息的目标openId
    /// template_id： 模板消息ID
    /// goto_url： 点击消息后跳转的页面
    /// data: 模板消息对应的数据
    /// client_msg_id：防重入id
    pub async fn send_message_template(&self, touser:&str, template_id:&str, goto_url:&str, data:Value, client_msg_id:&str) -> Result<i64, MyErr> {
        let authorization_access_token = self.get_authorization_access_token().await?;
        let url = format!("{}{}", SEND_MESSAGE_TEMPLATE_URL, authorization_access_token);

        let post_data = json!({
            "touser" : touser,
            "template_id" : template_id,
            "url" : goto_url,
            "client_msg_id" : client_msg_id,
            "data" : data,
        });

        let obj = super::post_json_to_url(&url, post_data).await?; 

        let errcode = obj.get("errcode").and_then(Value::as_i64).unwrap_or(-1);
        if errcode == -1 {
            Err(MyErr::Msg(format!("发送模板消息失败，服务器返回信息:{:?}", obj)))
        } else if errcode == 0 {
            Ok(errcode)
        } else {
            let errmsg = obj.get("errmsg").and_then(Value::as_str).unwrap_or_default();
            Err(MyErr::Msg(format!("错误代码: {}, 内容{}", errcode, errmsg)))
        } 
    }

    /// 获取微信公众号网页签名
    pub async fn get_web_signature(&self, url: &str) -> Result<(String, i64, String), MyErr> {

        let js_api_ticket = self.get_js_api_ticket().await?;

        let nonce_str = util::random_str(8);
        let timestamp = util_date::now_seconds(); 
        let signature_str = format!("jsapi_ticket={}&noncestr={}&timestamp={}&url={}", js_api_ticket, nonce_str, timestamp, url);
        let signature = util_string::sha1(&signature_str);
	    //signature = gsha1.Encrypt(fmt.Sprintf(str, GetJsapiTicket(), nonceStr, timestamp, url))

        Ok((nonce_str, timestamp, signature))
    }

    /// 解密微信服务器传递过来的消息
    pub fn decrypt_msg(&self, timestamp: &str, nonce: &str, _encrypt_type: &str, msg_signature: &str, body: &str) -> Result<MessageDecXML, MyErr> {
        if let Ok(enc_xml) = from_str::<MessageEncXML>(&body) {
            log::info!("xml item: {:?}", enc_xml);

            if self.wechat.check_msg_signature(&msg_signature, &timestamp, &nonce, &enc_xml.encrypt) {
                // 如果校验正确，则开始解密
                let msg_xml = self.wechat.aes_decrypt_weixin_msg(&enc_xml.encrypt)?;
                log::info!("解密后xml: [{}]", msg_xml); 

                if let Ok(xml) = from_str::<MessageDecXML>(&msg_xml) {
                    Ok(xml)
                } else {
                    Err(MyErr::Msg("把结果 xml 序列化为 MessageDecXML 的时候发生了错误！".to_string()))
                } 
            } else {
                Err(MyErr::Msg("消息合法性验证失败".to_string()))
            }
        } else {
            Err(MyErr::Msg("解析XML数据错误".to_string()))
        }
    }

    /// 通过code换取网页授权 openid, access_token（网页用access_token）, refresh_token 等
    pub async fn get_openid_by_code(&self, code:&str) -> Result<String, MyErr> {

        let wechat_open = self.wechat.get_open();
        let component_access_token = wechat_open.get_component_access_token().await?;

        let url = format!("https://api.weixin.qq.com/sns/oauth2/component/access_token?appid={}&code={}&grant_type=authorization_code&component_appid={}&component_access_token={}",
            self.authorizer_app_id, 
            code,
            self.wechat.appid,
            component_access_token, 
        );

        let obj = super::get_json_to_url(&url).await?;
        if obj.get("openid").is_some() {
            // 如果 openid 存在，则表示读取成功
            let _access_token = obj.get("access_token").and_then(Value::as_str).unwrap_or_default();
            let _expires_in = obj.get("expires_in").and_then(Value::as_i64).unwrap_or(3600);
            let _refresh_token = obj.get("refresh_token").and_then(Value::as_str).unwrap_or_default();
            let openid = obj.get("openid").and_then(Value::as_str).unwrap_or_default(); 
            Ok(openid.to_string())
        } else {
            let errcode = obj.get("errcode").and_then(Value::as_i64).unwrap_or(-1);
            if errcode == -1 {
                Err(MyErr::Msg(format!("获取用户openId失败，服务器返回信息:{:?}", obj))) 
            } else {
                let errmsg = obj.get("errmsg").and_then(Value::as_str).unwrap_or_default();
                Err(MyErr::Errcode((errcode, errmsg.to_string())))
            } 
        } 
    }
    
}